package cn.sylinx.horm.dialect.tool;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import cn.sylinx.horm.model.anno.BlobType;
import cn.sylinx.horm.model.anno.Column;
import cn.sylinx.horm.model.anno.TextType;
import cn.sylinx.horm.model.cache.ModelCacheUtil;
import cn.sylinx.horm.model.cache.ModelFabric;
import cn.sylinx.horm.util.StrKit;

public class MySqlUtil {

    public static final String MYSQL_SINGLE_QUOTES = "`";

    public static final String MYSQL_DROP_TEMPLATE = "DROP TABLE IF EXISTS `%s`";

    public static final String MYSQL_CREATE_TEMPLATE = "CREATE TABLE IF NOT EXISTS `%s` (";

    public static final String MYSQL_ROW_PK = " PRIMARY KEY (`id`) ";

    public static final String MYSQL_ENGINE = ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin";

    public static final String MYSQL_ALTER_TABLE = "ALTER TABLE `%s`";

    public static final String MYSQL_ALTER_TABLE_ADD_COL = "ADD COLUMN";

    public static final String MYSQL_ALTER_TABLE_MODIFY_COL = "MODIFY COLUMN";

    protected final static Map<String, String> MYSQL_JAVA_JDBC_MAP = new HashMap<String, String>();

    static {

        MYSQL_JAVA_JDBC_MAP.put("java.io.Serializable", "varchar");
        MYSQL_JAVA_JDBC_MAP.put("java.util.Date", "datetime");
        MYSQL_JAVA_JDBC_MAP.put("java.sql.Date", "datetime");
        MYSQL_JAVA_JDBC_MAP.put("java.lang.String", "varchar");
        MYSQL_JAVA_JDBC_MAP.put("java.lang.String.MIN", "text");
        MYSQL_JAVA_JDBC_MAP.put("java.lang.String.MEDIUM", "mediumtext");
        MYSQL_JAVA_JDBC_MAP.put("java.lang.String.LONG", "longtext");
        MYSQL_JAVA_JDBC_MAP.put("java.lang.Long", "bigint");
        MYSQL_JAVA_JDBC_MAP.put("java.lang.Byte", "tinyint");
        MYSQL_JAVA_JDBC_MAP.put("java.lang.Short", "smallint");
        MYSQL_JAVA_JDBC_MAP.put("java.math.BigInteger", "bigint(20) unsigned");
        MYSQL_JAVA_JDBC_MAP.put("java.math.BigDecimal", "decimal");
        MYSQL_JAVA_JDBC_MAP.put("java.lang.Integer", "int");
        MYSQL_JAVA_JDBC_MAP.put("java.lang.Float", "float");
        MYSQL_JAVA_JDBC_MAP.put("java.lang.Double", "double");
        MYSQL_JAVA_JDBC_MAP.put("java.lang.Boolean", "bit(1)");
        MYSQL_JAVA_JDBC_MAP.put("[B", "blob");
        MYSQL_JAVA_JDBC_MAP.put("[B.MIN", "blob");
        MYSQL_JAVA_JDBC_MAP.put("[B.MEDIUM", "mediumblob");
        MYSQL_JAVA_JDBC_MAP.put("[B.LONG", "longblob");
        // 枚举 java.lang.Enum
        MYSQL_JAVA_JDBC_MAP.put("java.lang.Enum", "varchar");
    }

    protected Class<? extends Object> clz;
    protected List<Field> fields;
    protected String tableName;
    protected String tableDesc;
    protected Map<String, String> attrMapping;
    protected Map<String, Column> fieldDesc;
    protected Map<String, Field> fieldMap;

    public MySqlUtil(Class<? extends Object> clz) {

        this.clz = clz;

        if (clz == null) {
            throw new RuntimeException("clz is null or path is null");
        }

        ModelFabric modelFabric = ModelCacheUtil.getModelFabric(this.clz);
        fields = modelFabric.getFields();
        tableName = modelFabric.getTableName();
        tableDesc = modelFabric.getTable() == null ? modelFabric.getTableName() : modelFabric.getTable().desc();
        attrMapping = modelFabric.getAttrMapping();
        fieldDesc = modelFabric.getFieldDesc();
        fieldMap = modelFabric.getFieldMap();

    }

    /**
     * 批量生成脚本
     * 
     * @param clzArray
     * @return
     */
    public static String generateSql(Class<?>... clzArray) {
        if (clzArray == null || clzArray.length == 0) {
            return "";
        }

        StringBuilder sb = new StringBuilder();
        for (Class<?> clz : clzArray) {
            sb.append(new MySqlUtil(clz).generateSql()).append("\n");
        }
        return sb.toString();
    }

    /**
     * 生成表的SQL
     * 
     * @return
     */
    public String generateSql() {

        StringBuilder sb = new StringBuilder();
        sb.append(String.format(MYSQL_CREATE_TEMPLATE, tableName)).append("\n");
        List<Field> sortField = new ArrayList<Field>();
        for (Field field : fields) {
            if (field.getName().equals("id")) {
                sortField.add(0, field);
            } else {
                sortField.add(field);
            }
        }
        for (Field field : sortField) {
            sb.append(generateSingleColumn(field)).append("\n");
        }
        sb.append(MYSQL_ROW_PK).append("\n");
        sb.append(MYSQL_ENGINE).append(StrKit.isBlank(tableDesc) ? "" : (" COMMENT='" + tableDesc + "'")).append(";\n");

        return sb.toString();
    }

    private String generateSingleColumn(Field field) {

        StringBuilder sb = new StringBuilder();

        String propertyName = field.getName();
        Column cd = fieldDesc.get(propertyName);
        String javaType = getFieldType(field);

        String jdbcType = toJdbcType(javaType);
        String columName = attrMapping.get(propertyName);
        if (StrKit.isBlank(columName)) {
            columName = propertyName;
        }

        String column_template = MYSQL_SINGLE_QUOTES + "%s" + MYSQL_SINGLE_QUOTES + " %s,";

        int len = 0, precision = 0;
        boolean blob = false, text = false;
        BlobType bt = BlobType.MIN;
        TextType tt = TextType.MIN;
        String dv = "";

        String comment = null;
        boolean nullable = true;

        if (cd != null) {
            len = cd.len();
            precision = cd.precision();
            comment = cd.value();
            nullable = cd.nullable();
            blob = cd.blob();
            text = cd.text();
            bt = cd.blobType();
            tt = cd.textType();
            dv = cd.defaultValue();
        }

        if (blob) {
            // 二进制判断
            String key = javaType + "." + bt.name();
            jdbcType = toJdbcType(key);
        } else if (text) {
            // 文本
            String key = javaType + "." + tt.name();
            jdbcType = toJdbcType(key);
        } else if ("varchar".equals(jdbcType)) {
            jdbcType = jdbcType + "(" + (len > 0 ? len : 50) + ")";
        } else if ("decimal".equals(jdbcType)) {
            int truelyLen = len > 0 ? len : 10;
            int truelyPrcision = precision >= 0 ? precision : 0;
            jdbcType = jdbcType + "(" + truelyLen + "," + truelyPrcision + ")";
        } else if (len > 0) {
            jdbcType = jdbcType + (precision == 0 ? "(" + len + ")" : "(" + len + "," + precision + ")");
        }

        if (StrKit.isNotBlank(dv)) {
            // 默认值不为空
            jdbcType = jdbcType + " DEFAULT " + dv;
        } else {
            // 空判断
            jdbcType = jdbcType + (nullable ? " DEFAULT NULL" : " NOT NULL");
        }

        // COMMENT
        if (StrKit.isNotBlank(comment)) {
            jdbcType = jdbcType + " COMMENT '" + comment + "'";
        }

        if ("id".equals(field.getName())) {
            // id主键
            jdbcType = "bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键'";
        }

        sb.append(String.format(column_template, columName, jdbcType));

        return sb.toString();
    }

    /**
     * 获取javaType对应的 jdbcType
     * 
     * @param javaType
     * @return
     */
    protected String toJdbcType(String javaType) {

        return MYSQL_JAVA_JDBC_MAP.get(javaType);
    }

    /**
     * 获取字段类型
     * 
     * @param field
     * @return
     */
    protected String getFieldType(Field field) {

        Class<?> clz = field.getType();
        if (clz.isEnum()) {
            // 枚举，使用字符串
            return "java.lang.Enum";
        }

        return clz.getName();
    }

    public List<Field> getFields() {
        return fields;
    }

    public String getTableName() {
        return tableName;
    }

    public void setTableName(String tableName) {
        this.tableName = tableName;
    }
}
